﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Utils.SoftReferences
{
    /// <summary>
    /// A set of soft references which can all be refreshed to point at updated targets in one operation.
    /// </summary>
    [Serializable]
    public class SoftRefSet
    {
        int _lowestUnallocatedId = 0;

        List<SoftRef> _softRefs = new List<SoftRef>();

        /// <summary>
        /// Refresh all the references in this set using the supplied enumerable set of updated objects
        /// </summary>
        /// <param name="referees"></param>
        public void Refresh(IEnumerable<ISoftRefTarget> referees)
        {
            foreach (var softRef in _softRefs)
            {
                ISoftRefTarget newIncarnation = referees.FirstOrDefault(r => r.Id == softRef.TargetId);

                if (newIncarnation == null)
                {
                    throw new ApplicationException("SoulReference #" + softRef.TargetId + " was not supplied in the fresh list of referees.");
                }

                softRef.SetTarget(newIncarnation);
            }
        }


        public SoftRef<T> Register<T>(T soul) where T : class, ISoftRefTarget
        {
            var newRef = new SoftRef<T>(soul);

            _softRefs.Add(newRef);

            return newRef;
        }

        public int AllocateNewId()
        {
            return _lowestUnallocatedId++;
        }
    }

    /// <summary>
    /// Base type for all generic SoftReference
    /// </summary>
    [Serializable]
    public class SoftRef
    {
        int _targetId;

        [NonSerialized]
        protected ISoftRefTarget _target;

        public SoftRef(ISoftRefTarget target)
        {
            _target = target;
            _targetId = _target.Id;
        }

        public T GetTargetAs<T>() where T : class
        {
            return _target as T;
        }

        public void SetTarget(ISoftRefTarget newTarget)
        {
            _target = newTarget;
        }

        public int TargetId
        {
            get
            {
                return _targetId;
            }
            protected set
            {
                _targetId = value;
            }
        }
    }

    /// <summary>
    /// Must be implemented by an object in order to be referenced by a SoftReference
    /// </summary>
    public interface ISoftRefTarget
    {
        int Id { get; }
    }

    /// <summary>
    /// Classes can inherit from BaseSoftRefTarget as a shortcut to implementing soft-reference-ability. If it's not
    /// appropriate or possible to do so, just implement ISoftRefTarget yourself. See if I care.
    /// </summary>
    [Serializable]
    public class BaseSoftRefTarget : ISoftRefTarget
    {
        public BaseSoftRefTarget()
        {
        }

        readonly int id;

        public BaseSoftRefTarget(SoftRefSet softRefSet)
        {
            id = softRefSet.AllocateNewId();
        }

        public int Id
        {
            get { return id; }
        }
    }

    /// <summary>
    /// An indirect reference to a re-incarnatable "mutable object reference". One "thing" might, over time, have many incarnations. Each incarnation is its own object.
    /// 
    /// All incarnations of an object will have the same UniqueIdentity. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SoftRef<T> : SoftRef where T : class, ISoftRefTarget
    {
        public SoftRef(T target) : base(target)
        {
        }

        public T Target
        {
            get
            {
                return base.GetTargetAs<T>();
            }
            set
            {
                _target = value;
            }
        }
    }
}
